using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Collections;

namespace MiddlewareServer.Socket
{

    class ClientObj
    {

        public delegate void ReadDelegate(IPAddress ip, byte[] data);
        private ReadDelegate _readDelegate;
        
        public delegate void DisConnectDelegate(IPAddress ip);
        private DisConnectDelegate _disconnectDelegate;

        private TcpClient _tcp;
        private IPAddress _ip;
        private BinaryReader _reader;
        private BinaryWriter _writer;

        private SocketServer _server;
        private const int _msgLenght = 8;
        private byte[] _data = new byte[_msgLenght];

        public ClientObj(TcpClient tcp, ReadDelegate readDelegate, DisConnectDelegate disconnectDelegate)
        {
            _tcp = tcp;
            _readDelegate += readDelegate;
            _disconnectDelegate += disconnectDelegate;
            try
            {
                Console.WriteLine("[CO]::Constructor");
                _reader = new BinaryReader(_tcp.GetStream());
                _writer = new BinaryWriter(_tcp.GetStream());
            }
            catch (Exception ex)
            {
            }
        }

        public void Run(object obj)
        {
            _server = (SocketServer)obj;
            _ip = ((IPEndPoint)_tcp.Client.RemoteEndPoint).Address;
            _server.ConnectClient(_ip);

            Console.Out.WriteLine("[CO]::Run");
            try
            {
                while (_tcp.Connected)
                {
                    _reader.Read(_data, 0, _msgLenght);

                    //Interpret header
                    HeaderStruct hdr = BytesToStruct(_data);
                    Console.Out.WriteLine("HDR TO: {0} {1}",hdr.msgType,hdr.msgLength);

                    //Prepare for rest of structure with data:
                    byte[] packetData = new byte[hdr.msgLength];

                    //Waiting for real pocket data
                    _reader.Read(packetData, 0, hdr.msgLength - _msgLenght);

                    MemoryStream ms = new MemoryStream(new byte[hdr.msgLength], 0, hdr.msgLength, true, true);
                    ms.Write(_data, 0, _msgLenght);
                    ms.Write(packetData, 0, hdr.msgLength - _msgLenght);
                    byte[] mergedData = ms.GetBuffer();

                    if (_readDelegate != null)
                    {
                        _readDelegate(_ip, mergedData);
                    }
                    else
                        Console.Out.WriteLine("delegate is empty");
                }

                if (_disconnectDelegate != null)
                {
                    _disconnectDelegate(_ip);
                }
                else
                {
                    Console.Out.WriteLine("delegate is empty");
                }
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex);
            }

            Console.Out.WriteLine("close");
        }

        public void Write(byte[] data)
        {
            try
            {
                Console.Out.WriteLine("WRITE");
                _writer.Write(data, 0, data.Length);
                _writer.Flush();
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine(ex);
            }
        }

        public HeaderStruct BytesToStruct(byte[] dataIn)
        {
            GCHandle hDataIn = GCHandle.Alloc(dataIn, GCHandleType.Pinned);
            HeaderStruct ys = (HeaderStruct)Marshal.PtrToStructure(hDataIn.AddrOfPinnedObject(),
            typeof(HeaderStruct));
            hDataIn.Free();
            return ys;
        }

        //To be removed if not needed
        public static object RawDeserialize(byte[] rawData, int position, Type anyType)
        {
            int rawsize = Marshal.SizeOf(anyType);
            if (rawsize > rawData.Length)
                return null;
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(rawData, position, buffer, rawsize);
            object retobj = Marshal.PtrToStructure(buffer, anyType);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }
    }
}
